package org.yawnage.framework 
{
	import org.yawnage.base.BaseKeyMapper;
	import org.yawnage.framework.server.BrokerPublisher;
	import org.yawnage.framework.server.Publisher;
	import org.yawnage.framework.server.ServiceLayer;
	import flash.utils.Dictionary;
	import org.yawnage.interfaces.framework.IBroker;
	import org.yawnage.interfaces.framework.IClient;
	import org.yawnage.interfaces.framework.IPublisher;
	import org.yawnage.interfaces.framework.IServiceLayer;
	import org.yawnage.interfaces.framework.IServiceMap;
	import org.yawnage.interfaces.framework.ISubscriber;
	import org.yawnage.interfaces.framework.ITransportLayer;
	 /**
	  * <p>//TODO:To be written</p>
	  * @author Berry Jordaan
	  * @date 2011/09/06 08:57 AM
	  * @copyright  Copyright © Berry/James 2011
	  * @version 1.0.0.0
	  * @description
	  * @history Berry Jordaan on 2011/09/06 08:57 AM: Created
	  */
	 public final class Broker extends BaseKeyMapper implements IBroker
	{

		//************** Enums **************

		//************** Public *************
		
		//************** Private ************
		private static var _instance	:Broker = null;
		private static var _debug		:Boolean = false;
		private static var _publisherToTest:String;
		private static var _inc			:int = 0;
		private var _clientMap			:IClient;
		
		
		//************** protected **********
		
		
		public function Broker(enforcer:singletonEnforcer)
		{
			if (enforcer==null) {
				throw new Error("Can't Instantiate Broker");
			}
		}
		
		/**
		 * Returns a singular instance
		 */
		public static function get instance():Broker {
			if (_instance ==  null) {
				_instance = new Broker(new singletonEnforcer);
				_instance.setClassDescription("org.yawnage.framework.Broker");
				
				//Init objects here
				_instance._clientMap = new ClientMap();
				_instance.register(new BrokerPublisher());
			}
			return _instance;
		}
		
		override public function dispose():void 
		{
			super.dispose();
		}
		
		/* INTERFACE org.yawnage.base.interfaces.framework.IBroker */
		
		public function register(publisher:Publisher):void 
		{
			if (hasObject(publisher.name)) 
			{
				throw new Error("ERROR: Duplicate Publisher ["+publisher.name+"] names found.");
			}
			Publisher(addObject(publisher)).defineServices();
		}
		
		public function unRegisterAll():void 
		{
			removeAllObjects();
			_trace("NOTE: All publishers removed.", this, 0xFF00FF);
		}
		
		public function unRegisterByName(publisherNameP:String):void 
		{
			removeObject(publisherNameP);
			_trace("NOTE: All publisher ["+publisherNameP+"] removed.", this, 0xFF00FF);
		}
		
		public function printPublishers():void 
		{
			_trace("**** PUBLISHERS LIST ****");
			var L:int = _mapperList.length;
			for (var i:int = 0; i < L ; i++) 
			{
				_trace("   "+(i+1)+") "+_mapperList[i]+"   internalID:"+IPublisher(_mapperList[i]).internalID);
			}
			_trace("**** END ****");
		}
		
		public function printServices(publisherNameP:String):void 
		{
			IServiceMap(publisherByName(publisherNameP)).printServices();
		}
		
		public function addServicesToPublisher(publisherP:Publisher, serviceP:IServiceLayer):void 
		{
			publisherP.registerService(publisherP, serviceP.key);
		}
		
		public function printAll():void 
		{
			try{
				_trace("****** BEGIN LIST ALL ******", this, 0x808080);
				_trace("------ "+_mapperList.length+" Publishers -------", this, 0x000080);
				var L:int = _mapperList.length;
				for (var i:int = 0; i < L ; i++) 
				{
					var p:Publisher = _mapperList[i] as Publisher;
					_trace("   +PUBLISHER:" + p.key +" - " + p.toString(), this, 0x000080);
					var sDic:Dictionary = p.serviceList();
					for (var sT:String in sDic) {
						var svce:ServiceLayer 	=  sDic[sT];
						var brdge:Bridge 		= _clientMap.getBridge(p.key, sT);
						var lst:Dictionary		= brdge.returnSubscribers();

						_trace("   |   +--SERVICE_NAME:[" + svce.key + "] ID:[" + svce.internalID + "] PUBLISHER:" + svce.publisher+" NUM_SUBSCRIBERS:"+brdge.numberSubscribers, this, 0x000080);
						for (var subsc:String in lst) {
							_trace("   |   |   +--Subcsriber SCOPE:"+lst[subsc].subscriber, this, 0x000080);
						}
					}
				}
			}catch (e:Error) {
				_trace(e.message, this, 0xFF0000);
				_trace(e.getStackTrace(), this, 0xFF0000);
			}
		}
		
		public function printAllPublisherObjects():void 
		{
			var L:int = _mapperList.length;
			for (var i:int = 0; i < L ; i++) 
			{
				_trace(_mapperList[i], this, 0x800040);
			}
		}
		
		public function publisherID(publisherNameP:String):int 
		{
			return IPublisher(getObjectbyKey(publisherNameP)).internalID;
		}
		
		public function publisherByName(publisherNameP:String):IPublisher 
		{
			return IPublisher(getObjectbyKey(publisherNameP));
		}
		
		public function isPublisher(publisherNameP:String):Boolean 
		{
			return hasObject(publisherNameP);
		}
		
		public function integrityCheck():Boolean 
		{
			_trace("********** INTEGRITY CHECK **********", this, 0x008000);
			var num:int = _clientMap.integrityCheck();
			_trace("The integrity check found "+num+" issues.", this, 0xFF0000);
			return (num > 0) ? true : false;
		}
		
		public function subscribe(subscriberP:ISubscriber, callBackP:Function, publisherNameP:String, serviceNameP:String, priority:int = 0):void 
		{
			var br:Bridge = _clientMap.getBridge(publisherNameP, serviceNameP);
			br.subscribe(subscriberP, callBackP, priority);
		}
		
		public function unSubscribe(subscriberP:ISubscriber, callBackP:Function, publisherNameP:String, serviceNameP:String):void 
		{
			_clientMap.getBridge(publisherNameP, serviceNameP).unSubscribe(subscriberP, callBackP);
		}
		
		public function unSubscribeAllFromPublisher(publisherNameP:String):void 
		{
			_clientMap.unSubscribeAllFromPublisher(publisherNameP);
		}
		
		public function unSubscribeAllFromService(publisherNameP:String, serviceNameP:String):void 
		{
			_clientMap.unSubscribeAllFromService(publisherNameP, serviceNameP);
		}
		
		public function unSubscribeAll():void 
		{
			var L:int = _mapperList.length;
			for (var i:int = 0; i < L ; i++) 
			{
				unSubscribeAllFromPublisher(_mapperList[i].key);
			}
		}
		
		public function syndicate(publisherP:Publisher, serviceNameP:String, transportLayerP:ITransportLayer = null):void 
		{
			//Test for publisher
			if (!isPublisher(publisherP.key)) {
				throw new Error("ERROR: Publisher ["+publisherP.key+"] not registered.");
			}
			//Test for service
			if (!IServiceMap(publisherP).isService(serviceNameP)) {
				throw new Error("ERROR: Service ["+serviceNameP+"] not registered for Publisher ["+publisherP.key+"].");
			}
			
			try {
				if(!Broker.debug){
					_clientMap.getBridge(publisherP.key, serviceNameP).forwardMessage(transportLayerP);
				}else {
					_clientMap.getBridge(publisherP.key, serviceNameP).debugForwardMessage(transportLayerP);
				}
			}catch (e:Error) {
				_trace(e.message, "error", 0xFF0000, this);
				_trace(e.getStackTrace(),"error", 0xFF0000, this);
			}
		}

		public function ready():void 
		{
			syndicate(Publisher(publisherByName(BrokerPublisher.PUBLISHER_NAME)), BrokerPublisher.SERVICE_READY) ;
			_trace("Broker ready!", this, 0x008040);
		}

		public static function set debug(value:Boolean):void 
		{
			_debug = value;
		}
		
		public static function get debug():Boolean 
		{
			return _debug;
		}
		
		static public function get publisherToTest():String 
		{
			return _publisherToTest;
		}
		
		static public function set publisherToTest(value:String):void 
		{
			_publisherToTest = value;
		}
		
		/**
		 * 
		 */
		public static function get internalID():Number {
			++_inc;
			return _inc;
		}
		
	}
}
class singletonEnforcer { };